﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NEH_SA.Model;
using NEH_SA.Utilities;

namespace NEH_SA.SA
{
    public static class SimulatedAnnealing
    {
        public static double TEnd = -2 / Math.Log(0.5);
        public static double LiczbaIteracji = 10000;

        /// <summary>
        /// Generuje rozwiazanie problemu metodą symulowanego wyżarzania
        /// </summary>
        /// <param name="problem">Instancja problemu</param>
        /// <returns></returns>
        public static Rozwiązanie WygenerujeRozwiązanie(Problem problem)
        {
            var rand = new Random();
            Debug.WriteLine(string.Format("Start: {0}", problem.Nazwa));
            //Generuj początkową permutacje
            var najlepszaPermutacja = Tools.GenerujPoczątkowąPermutacje(problem.LiczbaZadań, rand);
            var najlepszeCMax = najlepszaPermutacja.ObliczCMax(problem);

            var aktualnaPermutacja = najlepszaPermutacja.ToList();
            var aktualneCMax = najlepszeCMax;
            var deltaMax = problem.ZadaniaLista.Max(zadanie => zadanie.CzasyWykonywańLista.Sum());
            double tStart = -(deltaMax / 2.0) / Math.Log(0.5);
            double temperatura = tStart;
            for (int i = 0; i < LiczbaIteracji; i++)
            {
                //Generuj nową permutacje
                var chwilowaPermutacja = aktualnaPermutacja.GenerujMutację(rand);
                var chwiloweCMax = chwilowaPermutacja.ObliczCMax(problem);

                //Oblicz temperature - zmien wartosc co 100 iteracji
                if (i % 100 == 0 && i != 0)
                {
                    temperatura = (temperatura) / (1 + temperatura * ((tStart - TEnd) / (99 * tStart * TEnd)));
                }

                // zamień permutacje jeżeli lepsze 
                if (chwiloweCMax <= aktualneCMax)
                {
                    aktualnaPermutacja = chwilowaPermutacja;
                    aktualneCMax = chwiloweCMax;
                }
                // lub zamien permutacje jeżeli gorsze z pewnym prawdopodobieństwem
                else
                {
                    var delta = chwiloweCMax - aktualneCMax;
                    if (rand.NextDouble() < Math.Exp(-delta / temperatura))
                    {
                        aktualnaPermutacja = chwilowaPermutacja;
                        aktualneCMax = chwiloweCMax;
                    }

                }
                // jezeli lepsze jest lepsze niz optymalne to zamien optymalne
                if (aktualneCMax < najlepszeCMax)
                {
                    najlepszaPermutacja = aktualnaPermutacja;
                    najlepszeCMax = aktualneCMax;
                }
            }
            Debug.WriteLine(string.Format("End: {0}", problem.Nazwa));

            return new Rozwiązanie(najlepszaPermutacja, najlepszeCMax, problem.Nazwa, Algorytm.Sa);
        }

        /// <summary>
        /// Generuję nową permutację na zaszadzie mutacji.
        /// </summary>
        /// <param name="permutacja">Permutacja oryginalna</param>
        /// <param name="random">Klasa Random</param>
        /// <returns></returns>
        private static List<int> GenerujMutację(this List<int> permutacja, Random random)
        {
            var indexA = random.Next(0, permutacja.Count);
            int indexB;
            while ((indexB = random.Next(0, permutacja.Count)) == indexA)
            {
            }
            var result = permutacja.Swap(indexA, indexB);

            return result;
        }
    }
}
